Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Runtime (recursive) type-checking for JavaScript.
$ npm install rttc --save
Want to coerce a value to match a particular type?
var rttc = require('rttc');
rttc.coerce({ firstName: 'string'}, {firstName: 13375055});
// => { firstName: "13375055" }
rttc.coerce({ firstName: 'string'}, {something: 'totally incorrect'});
// => { firstName: "" }
// (when confronted with something totally weird, `.coerce()` returns the "base value" for the type)
Want to throw an Error if a value doesn't match a particular type?
rttc.validateStrict({ firstName: 'string'}, {firstName: 13375055});
// throws error
// (`.validateStrict()` demands a value that is precisely the correct type)
rttc.validateStrict({ firstName: 'string'}, {firstName: '13375055'});
// does not throw
Or if you want to be a little more forgiving:
rttc.validate({ firstName: 'string'}, {something: 'totally incorrect'});
// throws error
rttc.validate({ firstName: 'string'}, {firstName: 45});
// => "45"
// (when confronted with minor differences, `.validate()` coerces as needed to make stuff fit)
Not sure how to build a type schema for use with .coerce()
or .validate()
? Use .infer()
to build one from an example value you've got laying around:
rttc.infer({ firstName: 'Rosella', lastName: 'Graham', friends: ['Valencia', 'Edgar', 'Attis'] });
// => { firstName: 'string', lastName: 'string', friends: ['string'] }
note that when inferring an array type, the first item of the example is used as a pattern- assuming homogeneity (i.e. that all items will look the same)
You can do this with anything-- here's a more advanced version to show what I mean:
rttc.infer([{ upstream: '===', fieldName: 'photos', files: [{getFile: '->', fileName: 'whatever', numBytes: 34353, meta: '*' }] }]);
// =>
// [
// {
// upstream: 'ref',
// fieldName: 'string',
// files: [
// {
// getFile: 'lamda',
// fileName: 'string',
// numBytes: 'number',
// meta: 'json'
// }
// ]
// }
// ]
Note that all of the validation and coercion strategies used in this modules are recursive through the keys of plain old JavaScript objects and the indices of arrays.
Each type can be validated or coerced against. If coercion fails, the "base value" for the type will be used.
Also note that all types below may be expressed recursively within faceted dictionaries and patterned arrays. If those words don't make sense, keep reading, you'll see what I mean.
There are 10 different types recognized by rttc
:
type | rttc example notation | base value |
---|---|---|
string | 'any string like this' | '' |
number | 1337 (any number) | 0 |
boolean | false (or true ) | false |
lamda | function anyFunction(){ /* any function */ } | function () { throw new Error('Not implemented! (this function was automatically created by rttc'); }; |
generic dictionary | {} | {} (empty dictionary) |
generic array | [] | [] (empty array) |
json | '*' | null |
ref | '===' | undefined |
faceted dictionary (recursive) | {...} (i.e. w/ keys) | {...} (w/ all expected keys and their base values) |
pattern array (recursive) | [...] (i.e. w/ 1 item) | [] (empty array) |
example: 'stuff'
The string type accepts any string.
example: 323
The number type accepts numbers like 0
, -4
, or 235.3
. Anathemas like Infinity
, -Infinity
, NaN
, and -0
are all coerced to zero.
example: false
The boolean type accepts true
or false
.
example: ->
The lamda type accepts any function.
example: {}
The generic dictionary type accepts any JSON-serializable dictionary.
Dictionaries that have been validated/coerced against the generic dictionary type:
Error
instances get stringified into empty objects. Instead, rttc turns them into human-readable strings by reducing them to their .stack
property (this includes the error message and the stack trace w/ line numbers)RegExp
instances get stringified into empty objects. Instead, rttc turns them into human-readable strings like '/some regexp/gi'
function()
instances get stringified into empty objects. Instead, rttc turns them into human-readable strings like 'function doStuff (a,b) { console.log(\'wow I can actually read this!\'); }'
example: {...}
The faceted dictionary type is any dictionary type schema with at least one key. Extra keys in the actual value that are not in the type schema will be stripped out. Missing keys will cause .validate()
to throw.
Dictionary type schemas (i.e. plain old JavaScript objects nested like {a:{}}
) can be infinitely nested. Type validation and coercion will proceed through the nested objects recursively.
{
id: 'number',
name: 'string',
isAdmin: 'boolean',
mom: {
id: 'number',
name: 'string',
occupation: {
title: 'string',
workplace: 'string'
}
}
}
example: []
Arrays that have been validated/coerced against the generic array type:
Error
instances get stringified into empty objects. Instead, rttc turns them into human-readable strings by reducing them to their .stack
property (this includes the error message and the stack trace w/ line numbers)RegExp
instances get stringified into empty objects. Instead, rttc turns them into human-readable strings like '/some regexp/gi'
function()
instances get stringified into empty objects. Instead, rttc turns them into human-readable strings like 'function doStuff (a,b) { console.log(\'wow I can actually read this!\'); }'
example: ['Margaret']
example: [123]
example: [true]
example: [[...]]
example: [{...}]
Array type schemas may be infinitely nested and combined with dictionaries or any other types.
Runtime arrays being validated/coerced against array type schemas will be homogeneous (meaning every item in the array will have the same type).
Undefined items will always be stripped out of arrays.
Also note that, because of this, when providing a type schema or type-inference-able example for an array, you only need to provide one item in the array, e.g.:
[
{
id: 'number',
name: 'string',
email: 'string',
age: 'number',
isAdmin: 'boolean',
favoriteColors: ['string'],
friends: [
{
id: 'number',
name: 'string'
}
]
}
]
example: '*'
This works pretty much like the generic array or generic dictionary type, with one major difference: the top-level value can be a string, boolean, number, dictionary, array, or null value.
Other than the aforementioned exception for null
, the generic JSON type follows the JSON-serializability rules from generic arrays and generic dictionaries.
example: '==='
This special type allows anything except undefined
at the top level (undefined is permitted at any other level). It also does not rebuild objects, which means it maintains the original reference (i.e. is ===
). It does not guarantee JSON-serializability.
The following is a high-level overview of important conventions used by the rttc
module. For detailed coverage of every permutation of validation and coercion, check out the declarative tests in the spec/
folder of this repository.
undefined
and null
valuesundefined
is never valid as a top-level value against ANY type, even mutable reference (===
)undefined
IS, however, allowed as an item in a nested array or value in a nested dictionary, but only against the mutable reference type (===
)null
is only valid at the top level against the JSON (*
) and mutable reference (===
) types.NaN
is only valid against the mutable reference type ('==='
)Infinity
and -Infinity
is only valid against the mutable reference type ('==='
)Infinity
and -Infinity
are only valid against example: '==='
+0
and -0
are always coerced to 0
(except against the mutable reference type)When coerced against the generic dictionary, generic array, or the generic json types, the following is true:
Error
instances are coerced to the string value of their .stack
property (i.e. the message + stack trace you're used to seeing in the terminal)Date
instances are coerced to the string value of running their .toJSON()
method (a ISO-8601 timestamp, e.g. '2015-05-24T15:16:48.999Z'
. This reflects the Date in GMT/UTC time, so is therefore timezone-agnostic).RegExp
instances are coerced to the string value you get from running their .toString()
method (e.g. '/foo/'
or '/^bar/gi'
).toString()
method (e.g. 'function someFunction (some,args,like,this,maybe){ /* and some kind of implementation in here prbly */ }'
)Stream
and Buffer
instances (from Node.js) are only valid against the mutable reference type.null
against the generic dictionary, generic array, or the generic json types.As mentioned above, every type has a base value.
""
0
false
'->'
), base value is a function that uses the standard machine fn signature and triggers its "error" callback w/ a message about being the rttc default (e.g. function(inputs,exits,env) { return exits.error(new Error('not implemented')); }
){}
) or a faceted dictionary type (e.g. {foo:'bar'}
), the base value is {}
.[]
), or a faceted/homogenous array type (e.g. [3]
or [{age:48,name: 'Nico'}]
), the base value is []
'*'
), base value is null
.'==='
), base value is undefined
.Note that, for both arrays and dictionaries, any keys in the schema will get the base value for their type (and their keys for their type, etc. -- recursive)
This package exposes a number of different utility methods. If you're interested in using any of these directly, we highly recommend you consider looking at machinepack-rttc, which provides a higher-level abstraction with better documentation.
The low-level reference below assumes you are willing/able to dig into the source code of this module for more information. So continue at your own risk!
Throws if the provided value is not the right type (recursive).
Either returns a (potentially "lightly" coerced) version of the value that was accepted, or it throws. The "lightly" coerced value turns "3"
into 3
, "true"
into true
, -4.5
into "-4.5"
, etc.
ALWAYS returns an acceptable version of the value, even if it has to mangle it to get there (i.e. by using the "base value" for the expected type. More on that below.)
undefined
])This function will use the provided typeSchema
to figure out where "lamda" values (functions) are expected, then will use eval()
to bring them back to life. Use with care.
false
], [dontStringifyFunctions=false
])This takes care of a few serialization edge-cases, such as:
.stack
property), and functions (unless dontStringifyFunctions
is set)[Circular]
)-Infinity
, Infinity
, and NaN
with 0undefined
or null
values. If allowNull
is set to true, null
values will not be stripped from the encoded string.undefined
], [unsafeMode=false
])Parse a stringified value back into a usable value.
This is basically just a variation on JSON.parse that calls rttc.hydrate()
first if unsafeMode
is enabled.
false
])Encode a value into a string.
This is basically just a variation on JSON.stringify that calls rttc.dehydrate()
first.
undefined
], [unsafeMode=false
])Parse a string from a human into something usable. If provided, typeSchema
will be used to make a better guess. If unsafeMode
is enabled, lamda functions will be hydrated.
The inverse of .parseHuman()
, this function encodes a string that, if run through .parseHuman()
would result in the given value.
Given a type schema, strip out generics ("ref", "json", {}, and []) to convert it into a strict type. In other words, this makes a type schema "strict", and the result of this function always passes rttc.isStrictType()
.
undefined
])Determine whether two values are equivalent using _.isEqual()
, but also look for expected lamda
values in the optional type schema and call toString()
on functions before comparing them.
This is the method used by
rttc
's own tests to validate that expected values and actual values match.
Guess the type schema from an example value.
Determine whether the given type schema is "strict" (meaning it is a string, number, boolean, lamda, faceted dictionary, or patterned array). If second argument (recursive
) is set to true
, then also recursively check the subkeys of faceted dictionaries and patterns of arrays in the type schema.
type | is strict? |
---|---|
string | yes (always) |
number | yes (always) |
boolean | yes (always) |
lamda | yes (always) |
{} (generic) | no |
[] (generic) | no |
{...} (faceted) | yes (maybe recursively) |
[...] (patterned) | yes (maybe recursively) |
json | no |
ref | no |
Given a type schema, return an array of up to n
unique sample values that would validate against it (in random order). n
defaults to 2 if left undefined.
Given a value, return its type as a human-readable string (this is not limited to rttc types-- it can return strings like "Error"
and "Date"
)
Given a value, return a human-readable string which represents it. This string is equivalent to a JavaScript code snippet which would accurately represent the value in code.
This is a lot like util.inspect(val, false, null)
, but it also has special handling for Errors, Dates, RegExps, and Functions (using dehydrate()
with allowNull
enabled.) The biggest difference is that everything you get from rttc.compile()
is ready for use as values in *
, {}
, or []
type machines, Treeline, Angular's rendering engine, and JavaScript code in general (i.e. if you were to append it on the right-hand side of var x =
, or if you ran eval()
on it)
Note that undefined values in arrays and undefined values of keys in dictionaries will be stripped out, and circular references will be handled as they are in util.inspect(val, false, null)
Useful for:
Here's a table listing notable differences between util.inspect()
and rttc.compile()
for reference:
value | util.inspect() | rttc.compile() |
---|---|---|
a function | [Function: foo] | 'function foo (){}' |
a Date | Tue May 26 2015 20:05:37 GMT-0500 (CDT) | '2015-05-27T01:06:37.072Z' |
a RegExp | /foo/gi | '/foo/gi/' |
an Error | [Error] | 'Error\n at repl:1:24\n...' |
a deeply nested thing | { a: { b: { c: [Object] } } } | { a: { b: { c: { d: {} } } } } |
a circular thing | { y: { z: [Circular] } } | { y: { z: '[Circular ~]' } } |
undefined | undefined | null |
[undefined] | [undefined] | [] |
{foo: undefined} | {foo: undefined} | {} |
Infinity | Infinity | 0 |
-Infinity | -Infinity | 0 |
NaN | NaN | 0 |
Readable (Node stream) | { _readableState: { highWaterMar..}} | null |
Buffer (Node bytestring) | <Buffer 61 62 63> | null |
MIT
© 2014 Mike McNeil, Cody Stoltman; © 2015 The Treeline Company
FAQs
Runtime type-checking for JavaScript.
The npm package rttc receives a total of 42,991 weekly downloads. As such, rttc popularity was classified as popular.
We found that rttc demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.